<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { searchUsersPage, getGradeList, getDepList } from '@/api/user'

// 分析类型：用户/群体
const analysisType = ref('user') // 'user' | 'group'

// 时间范围
const form = ref({
  startTime: '',
  endTime: ''
})

// 用户分析：选择用户
const selectedUser = ref(null)
const selectedUserId = computed(() => selectedUser.value?.userId || '')

// 群体分析：学院/年级（至少选择一个）
const group = ref({
  department: '',
  grade: ''
})
const departmentOptions = ref([])
const gradeOptions = ref([])

const loadGroupOptions = async () => {
  try {
    const [depRes, gradeRes] = await Promise.all([getDepList(), getGradeList()])
    if (depRes?.code === 1 && Array.isArray(depRes.data)) {
      departmentOptions.value = depRes.data
    }
    if (gradeRes?.code === 1 && Array.isArray(gradeRes.data)) {
      gradeOptions.value = gradeRes.data
    }
  } catch (e) {
    // 静默失败，避免打扰；需要时可提示
  }
}

onMounted(() => {
  loadGroupOptions()
})
// 清空流式输出（可选是否停止请求）
function clearStreamOutput(stop = false) {
  if (stop && portraitAbortController) {
    try { portraitAbortController.abort() } catch {}
    portraitAbortController = null
    streaming.value = false
  }
  streamText.value = ''
  streamError.value = ''
  streamHtml.value = ''
  portraitRawText.value = ''
}
// 关键操作变更时清空输出，避免旧内容干扰
// watch(analysisType, () => clearStreamOutput(true))
watch(selectedUserId, () => { if (analysisType.value === 'user') clearStreamOutput(true) })
watch(() => form.value.startTime, () => { if (analysisType.value === 'user') clearStreamOutput() })
watch(() => form.value.endTime, () => { if (analysisType.value === 'user') clearStreamOutput() })
watch(() => group.value.department, () => { if (analysisType.value === 'group') clearStreamOutput(true) })
watch(() => group.value.grade, () => { if (analysisType.value === 'group') clearStreamOutput(true) })

// 用户选择弹窗
const userDialogVisible = ref(false)
const userKeyword = ref('')
const lastKeyword = ref('')
const userLoading = ref(false)
const userRows = ref([])
const userTotal = ref(0)
const userPage = ref(1)
const userPageSize = ref(10)

const openUserDialog = () => {
  userDialogVisible.value = true
  userRows.value = []
  userTotal.value = 0
  userKeyword.value = ''
  lastKeyword.value = ''
  userPage.value = 1
  userPageSize.value = 10
  // 弹窗首次打开即加载第一页（无关键字）
  searchUsers(false, true)
}

const searchUsers = async (keepKeyword = false, allowEmpty = false) => {
  const kw = keepKeyword ? lastKeyword.value : (userKeyword.value || '').trim()
  if (!kw && !allowEmpty) {
    ElMessage.warning('请输入用户ID或姓名进行搜索')
    return
  }
  try {
    userLoading.value = true
    lastKeyword.value = kw
    const res = await searchUsersPage(kw, userPage.value)
    const ok = res && res.code === 1
    if (!ok) {
      ElMessage.error(res?.msg || '用户搜索失败')
      return
    }
    const data = res.data || { total: 0, rows: [] }
    userTotal.value = Number(data.total || 0)
    userRows.value = Array.isArray(data.rows) ? data.rows : []
  } catch (e) {
    ElMessage.error('用户搜索接口异常')
  } finally {
    userLoading.value = false
  }
}

const handleUserSizeChange = (size) => {
  userPageSize.value = size
  userPage.value = 1
  searchUsers(true, true)
}
const handleUserCurrentChange = (page) => {
  userPage.value = page
  searchUsers(true, true)
}

const chooseUser = (row) => {
  selectedUser.value = row
  userDialogVisible.value = false
}
const handleOpenUserDialogAndSearch = () => {
  openUserDialog()
}
// 提交分析（这里只整理参数，不进行实际绘图或表格展示）
const startAnalysis = () => {
  if (analysisType.value === 'user') {
    if (!form.value.startTime || !form.value.endTime) {
      ElMessage.error('请选择开始与结束时间')
      return
    }
    if (!selectedUserId.value) {
      ElMessage.error('请先选择用户')
      return
    }
    const params = {
      userId: selectedUserId.value,
      startTime: form.value.startTime,
      endTime: form.value.endTime
    }
    startUserPortraitStream(params)
  } else {
    const dep = (group.value.department || '').trim()
    const grd = (group.value.grade || '').trim()
    if (!dep && !grd) {
      ElMessage.error('群体分析至少选择学院或年级之一')
      return
    }
    const params = {
      department: dep || '',
      grade: grd || ''
    }
    startGroupFeaturesStream(params)
  }
}
// 流式用户画像输出
const streaming = ref(false)
const streamText = ref('')
const streamError = ref('')
const streamHtml = ref('')
const portraitRawText = ref('')
let portraitAbortController = null
const getUserToken = () => {
  try {
    const u = JSON.parse(localStorage.getItem('user'))
    return u && u.token ? u.token : ''
  } catch { return '' }
}

const startUserPortraitStream = async (params) => {
  try {
    if (portraitAbortController) {
      try { portraitAbortController.abort() } catch {}
      portraitAbortController = null
    }
    const q = new URLSearchParams(params)
    const url = `/api/ai/user-portrait?${q.toString()}`
    const token = getUserToken()
    portraitAbortController = new AbortController()
    const resp = await fetch(url, {
      method: 'GET',
      signal: portraitAbortController.signal,
      headers: {
        Accept: 'text/event-stream, text/plain; charset=utf-8, application/json',
        ...(token ? { token } : {})
      }
    })
    if (!resp.ok) {
      throw new Error(`请求失败：${resp.status}`)
    }
    const reader = resp.body?.getReader()
    if (!reader) {
      throw new Error('浏览器不支持ReadableStream')
    }
    const decoder = new TextDecoder('utf-8')
    streaming.value = true
    streamText.value = ''
    streamError.value = ''
    portraitRawText.value = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      const chunk = decoder.decode(value, { stream: true })
      portraitRawText.value += chunk
      streamText.value = formatPortraitText(portraitRawText.value)
      streamHtml.value = markdownToHtml(streamText.value)
    }
    // 完整结束后再跑一次格式化，避免尾段遗漏
    streamText.value = formatPortraitText(portraitRawText.value)
    streamHtml.value = markdownToHtml(streamText.value)
    streaming.value = false
    portraitAbortController = null
    ElMessage.success('用户画像流接收完成')
  } catch (e) {
    streamError.value = e?.name === 'AbortError' ? '已停止' : (e?.message || '流式请求异常')
    streaming.value = false
    portraitAbortController = null
  }
}

const startGroupFeaturesStream = async (params) => {
  try {
    if (portraitAbortController) {
      try { portraitAbortController.abort() } catch {}
      portraitAbortController = null
    }
    const q = new URLSearchParams(params)
    const url = `/api/ai/group-features?${q.toString()}`
    const token = getUserToken()
    portraitAbortController = new AbortController()
    const resp = await fetch(url, {
      method: 'GET',
      signal: portraitAbortController.signal,
      headers: {
        Accept: 'text/event-stream, text/plain; charset=utf-8, application/json',
        ...(token ? { token } : {})
      }
    })
    if (!resp.ok) {
      throw new Error(`请求失败：${resp.status}`)
    }
    const reader = resp.body?.getReader()
    if (!reader) {
      throw new Error('浏览器不支持ReadableStream')
    }
    const decoder = new TextDecoder('utf-8')
    streaming.value = true
    streamText.value = ''
    streamError.value = ''
    portraitRawText.value = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      const chunk = decoder.decode(value, { stream: true })
      portraitRawText.value += chunk
      streamText.value = formatPortraitText(portraitRawText.value)
      streamHtml.value = markdownToHtml(streamText.value)
    }
    streamText.value = formatPortraitText(portraitRawText.value)
    streamHtml.value = markdownToHtml(streamText.value)
    streaming.value = false
    portraitAbortController = null
    ElMessage.success('群体分析流接收完成')
  } catch (e) {
    streamError.value = e?.name === 'AbortError' ? '已停止' : (e?.message || '流式请求异常')
    streaming.value = false
    portraitAbortController = null
  }
}

const stopPortraitStream = () => {
  if (portraitAbortController) {
    try { portraitAbortController.abort() } catch {}
  }
}
function formatPortraitText(raw) {
  if (!raw) return ''
  let text = raw

  // 解析 SSE：同一事件内所有 data 行直接拼接为一段；事件之间用空格连接
  if (text.includes('data:')) {
    const events = text.split(/\r?\n\r?\n/)
    const out = []
    for (const ev of events) {
      const lines = ev.split(/\r?\n/)
      const dataJoined = lines
        .filter(l => l.startsWith('data:'))
        .map(l => l.slice(5))
        .join('')
      if (dataJoined) out.push(dataJoined)
    }
    text = out.join(' ')
  }

  // 统一清理回车
  text = text.replace(/\r/g, '')

  // 数字和符号修复（允许事件拼接产生的空格被正确合并）
  text = text.replace(/(\d)\s+(\.\d+)/g, '$1$2')
  text = text.replace(/(\d)\s+%/g, '$1%')
  text = text.replace(/(\d)\s+\./g, '$1.')

  // 压缩空白
  text = text.replace(/\s{2,}/g, ' ')

  // 中文与数字的粘连（移除人工换行引入的空格）
  text = text.replace(/[\u4e00-\u9fa5]\s+[\u4e00-\u9fa5]/g, (m) => m.replace(/\s+/g, ''))
  text = text.replace(/[\u4e00-\u9fa5]\s+\d/g, (m) => m.replace(/\s+/g, ''))
  text = text.replace(/\d\s+[\u4e00-\u9fa5]/g, (m) => m.replace(/\s+/g, ''))
  text = text.replace(/\d\s+\d/g, (m) => m.replace(/\s+/g, ''))

  // 仅在标题（加粗）处换行：支持 “- **标题**” 和 “N. **标题**”
  text = text.replace(/\s*-\s*\*\*/g, '\n- **')
  text = text.replace(/(\d+\.)\s*\*\*/g, (m, num) => `\n${num} **`)

  // 合并连续空行为单行
  text = text.replace(/\n{2,}/g, '\n')

  return text.trim()
}

function markdownToHtml(md) {
  if (!md) return ''
  // 先转义 HTML，避免注入
  let safe = md.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  // 加粗：**文本**
  safe = safe.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
  // 行内代码：`code`
  safe = safe.replace(/`([^`]+)`/g, '<code>$1</code>')
  // 将换行转换为 <br>
  safe = safe.replace(/\n/g, '<br>')
  return safe
}
</script>

<template>
  <div class="container">
    <h1>消费行为分析</h1><br></br>
    <el-form :inline="true" class="search-form">
      <el-form-item label="分析对象">
        <el-radio-group v-model="analysisType">
          <el-radio-button label="user">用户分析</el-radio-button>
          <el-radio-button label="group">群体分析</el-radio-button>
        </el-radio-group>
      </el-form-item>
      <!-- 将时间选择限定在用户分析模式下显示 -->
      <el-form-item v-if="analysisType === 'user'" label="开始时间">
        <el-date-picker v-model="form.startTime" type="date" value-format="YYYY-MM-DD" placeholder="选择开始时间" style="width: 160px" />
      </el-form-item>
      <el-form-item v-if="analysisType === 'user'" label="结束时间">
        <el-date-picker v-model="form.endTime" type="date" value-format="YYYY-MM-DD" placeholder="选择结束时间" style="width: 160px" />
      </el-form-item>

      <template v-if="analysisType === 'user'">
        <el-form-item>
          <el-button type="primary"  @click="handleOpenUserDialogAndSearch">选择用户</el-button>
        </el-form-item>
        <el-form-item v-if="selectedUserId">
          <el-tag type="success">已选择：{{ selectedUser?.userName }}（{{ selectedUserId }}）</el-tag>
        </el-form-item>
      </template>

      <template v-else>
        <el-form-item label="学院">
          <el-select v-model="group.department" placeholder="选择学院" style="width: 220px" filterable clearable>
            <el-option v-for="dep in departmentOptions" :key="dep" :label="dep" :value="dep" />
          </el-select>
        </el-form-item>
        <el-form-item label="年级">
          <el-select v-model="group.grade" placeholder="选择年级" style="width: 160px" clearable>
            <el-option v-for="g in gradeOptions" :key="g" :label="g" :value="g" />
          </el-select>
        </el-form-item>
      </template>

      <el-form-item>
        <el-button type="primary" @click="startAnalysis">开始分析</el-button>
      </el-form-item>
    </el-form>
    <div v-if="analysisType === 'user' || analysisType === 'group'" class="stream-box">
      <div class="stream-header">
        <span>{{ analysisType === 'user' ? '用户画像流输出' : '群体分析流输出' }}</span>
        <div>
          <el-button v-if="streaming" type="warning" size="small" @click="stopPortraitStream">停止</el-button>
          <el-button type="default" size="small" style="margin-left: 8px" @click="clearStreamOutput(true)">清空</el-button>
        </div>
      </div>
      <!-- 仅显示 Markdown 渲染，不再显示原始 textarea -->
      <div class="stream-render" v-html="streamHtml"></div>
      <div v-if="streamError" class="stream-error">{{ streamError }}</div>
    </div>
  </div>

  <!-- 选择用户弹窗 -->
  <el-dialog v-model="userDialogVisible" title="选择用户" width="900px" :close-on-click-modal="false">
    <div class="dialog-body">
      <div class="search-row">
        <el-input v-model="userKeyword" placeholder="输入用户ID或姓名，如U00021或用户21" clearable @keyup.enter="() => searchUsers(false)" />
        <el-button type="primary" :loading="userLoading" @click="() => searchUsers(false)" style="margin-left: 10px">搜索</el-button>
      </div>
      <div class="result-list" style="margin-top: 12px;">
        <el-table :data="userRows" border stripe size="small" :loading="userLoading">
          <el-table-column prop="userId" label="用户ID" width="120" />
          <el-table-column prop="userName" label="姓名" width="140" />
          <el-table-column prop="department" label="学院" width="180" />
          <el-table-column label="年级" width="100">
            <template #default="{ row }">
              <span>{{ row.userType === 0 ? (row.grade || '-') : '—' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="类型" width="90">
            <template #default="{ row }">
              <span>{{ row.userType === 0 ? '学生' : '老师' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="100" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" size="small" @click="chooseUser(row)">选择</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div style="margin-top: 8px; text-align: right;">
          <el-pagination
            v-model:current-page="userPage"
            :total="userTotal"
            layout="total, prev, pager, next, jumper"
            @current-change="handleUserCurrentChange"
          />
        </div>
      </div>
    </div>
    <template #footer>
      <el-button @click="userDialogVisible = false">关闭</el-button>
    </template>
  </el-dialog>
</template>

<style scoped>
.container { margin-top: 15px; margin-bottom: 15px; }
.search-form :deep(.el-form-item) { margin-right: 12px; }
.dialog-body { padding: 4px 8px; }
.search-row { display: flex; align-items: center; }
.result-list { margin-top: 12px; }
.user-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-bottom: 1px solid #eee; }
.user-title { font-weight: 600; }
.user-meta { margin-top: 4px; color: #666; font-size: 13px; display: flex; gap: 14px; }
.user-actions { flex-shrink: 0; }
.stream-box { margin-top: 12px; padding: 8px; border: 1px solid #eee; border-radius: 6px; }
.stream-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.stream-pre { white-space: pre-wrap; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 13px; line-height: 1.5; }
.stream-error { color: #d93025; margin-top: 6px; }
/* 调大用户画像文本框字体与行距 */
.stream-box :deep(.el-textarea__inner) { font-size: 16px; line-height: 1.8; }
/* 渲染区样式 */
.stream-render { margin-top: 8px; padding: 10px; background: #fafafa; border: 1px dashed #e5e5e5; border-radius: 6px; font-size: 16px; line-height: 1.8; }
</style>